﻿using System;
using System.Collections.Generic;
using System.Text;
using Navigant.Infrastructure;
using Navigant.Infrastructure.Cases;
using Navigant.Infrastructure.DomainBase;
using Navigant.Infrastructure.Helpers;
using Navigant.Model.Custodians;
using Navigant.Model.PreservationNotices;

namespace Navigant.Application
{
    /// <summary>
    /// An Application Layer class responsible for the coordination of 
    /// sending notices in real time and also sending batched notices.
    /// </summary>
    public static class PreservationNoticeSendingService
    {
        /// <summary>
        /// Sends a single <see cref="PreservationNotice"/> to a 
        /// <see cref="Custodian"/> for the current <see cref="ICase"/>.
        /// </summary>
        /// <param name="currentCase">An <see cref="ICase"/> instance.</param>
        /// <param name="custodian">The <see cref="Custodian"/> to whom the <see cref="PreservationNotice"/> is inteneed for.</param>
        /// <param name="preservationNoticeTemplateId">The identifier value for the <see cref="PreservationNoticeTemplate"/> from which the <see cref="PreservationNotice"/> instance is to be built from.</param>
        /// <param name="fromAddress">The email address of the person sending the <see cref="PreservationNotice"/>.</param>
        /// <param name="originalNotice">The original <see cref="PreservationNotice"/> if sending a reminder.</param>
        public static void SendNotice(ICase currentCase, Custodian custodian,
            int preservationNoticeTemplateId, string fromAddress, 
            PreservationNotice originalNotice)
        {
            // Do not send notices to Custodians who are not suppoosed to 
            // receive any messages 
            if (!custodian.CanReceiveMessages())
            {
                return;
            }

            // Make sure the Custodian is in a valid state
            if (custodian.GetBrokenRules().Count > 0)
            {
                return;
            }

            // Get a unit of work context
            IUnitOfWork unitOfWork = UnitOfWork.New();

            // Get a PreservationNoticeTemplate instance
            PreservationNoticeTemplateService templateService = new PreservationNoticeTemplateService(currentCase, unitOfWork);
            PreservationNoticeTemplate template = templateService.GetPreservationNoticeTemplate(preservationNoticeTemplateId);

            // See if this is a reminder notice or not
            bool reminder = (originalNotice != null);

            // Merge the template with the custodian to produce a notice
            PreservationNotice notice = template.Merge(custodian, reminder, fromAddress);

            // Get the list of attachments
            List<Attachment> attachments = new List<Attachment>(notice.Attachments);

            // If the notice is a reminder then try to send the original notice as an attachment
            if (reminder && 
                originalNotice.Template != null && 
                !string.IsNullOrEmpty(originalNotice.Template.Name) &&
                !string.IsNullOrEmpty(originalNotice.Body))
            {
                // Turn the original message body into a byte array
                byte[] message = UnicodeEncoding.UTF8.GetBytes(originalNotice.Body);

                // Add the original message as an attachment
                attachments.Add(new Attachment(message, 
                    string.Format("Original_Notice_{0}.html", originalNotice.Template.Name)));
            }

            // Email the notice
            EmailHelper.SendMessage(notice.To.Email, notice.FromAddress,
                notice.Subject, notice.Body, attachments, notice.Images);

            // Change the state of the notice and the Custodian
            notice.MessageSent(reminder);
            notice.To.NoticeSent(reminder);

            // Save the notice
            PreservationNoticeService service = new PreservationNoticeService(currentCase, unitOfWork);
            service.SavePreservationNotice(notice);

            // Save the Custodian
            CustodianService custodianService = new CustodianService(currentCase, unitOfWork);
            custodianService.SaveCustodian(custodian);

            // Commit the transaction
            unitOfWork.Commit();
        }

        /// <summary>
        /// Sends a single <see cref="PreservationNotice"/> to a 
        /// <see cref="Custodian"/> for the current <see cref="ICase"/>.
        /// </summary>
        /// <param name="currentCase">An <see cref="ICase"/> instance.</param>
        /// <param name="custodian">The <see cref="Custodian"/> to whom the <see cref="PreservationNotice"/> is inteneed for.</param>
        /// <param name="preservationNoticeTemplateId">The identifier value for the <see cref="PreservationNoticeTemplate"/> from which the <see cref="PreservationNotice"/> instance is to be built from.</param>
        /// <param name="fromAddress">The email address of the person sending the <see cref="PreservationNotice"/>.</param>
        public static void SendNotice(ICase currentCase, Custodian custodian,
            int preservationNoticeTemplateId, string fromAddress)
        {
            PreservationNoticeSendingService.SendNotice(currentCase, custodian, 
                preservationNoticeTemplateId, fromAddress, null);
        }

        /// <summary>
        /// Sends a single <see cref="PreservationNotice"/> to a 
        /// <see cref="Custodian"/> for the current <see cref="ICase"/>.
        /// </summary>
        /// <param name="currentCase">An <see cref="ICase"/> instance.</param>
        /// <param name="custodianId">The identifier value for the <see cref="Custodian"/> to whom the <see cref="PreservationNotice"/> is inteneed for.</param>
        /// <param name="preservationNoticeTemplateId">The identifier value for the <see cref="PreservationNoticeTemplate"/> from which the <see cref="PreservationNotice"/> instance is to be built from.</param>
        /// <param name="fromAddress">The email address of the person sending the <see cref="PreservationNotice"/>.</param>
        /// <param name="originalNotice">The original <see cref="PreservationNotice"/> if sending a reminder.</param>
        public static void SendNotice(ICase currentCase, int custodianId,
            int preservationNoticeTemplateId, string fromAddress, 
            PreservationNotice originalNotice)
        {
            CustodianService custodianService = new CustodianService(currentCase);
            Custodian custodian = custodianService.GetCustodian(custodianId);
            PreservationNoticeSendingService.SendNotice(currentCase, custodian,
                preservationNoticeTemplateId, fromAddress, originalNotice);
        }

        /// <summary>
        /// Sends a single <see cref="PreservationNotice"/> to a 
        /// <see cref="Custodian"/> for the current <see cref="ICase"/>.
        /// </summary>
        /// <param name="currentCase">An <see cref="ICase"/> instance.</param>
        /// <param name="custodianId">The identifier value for the <see cref="Custodian"/> to whom the <see cref="PreservationNotice"/> is inteneed for.</param>
        /// <param name="preservationNoticeTemplateId">The identifier value for the <see cref="PreservationNoticeTemplate"/> from which the <see cref="PreservationNotice"/> instance is to be built from.</param>
        /// <param name="fromAddress">The email address of the person sending the <see cref="PreservationNotice"/>.</param>
        /// <param name="originalNoticeId">The identifier of the original <see cref="PreservationNotice"/> if sending a reminder.</param>
        public static void SendNotice(ICase currentCase, int custodianId,
            int preservationNoticeTemplateId, string fromAddress,
            int originalNoticeId)
        {
            PreservationNoticeService service = new PreservationNoticeService(currentCase);
            PreservationNotice originalNotice = service.FindBy(originalNoticeId);
            PreservationNoticeSendingService.SendNotice(currentCase, custodianId,
                preservationNoticeTemplateId, fromAddress, originalNotice);
        }

        /// <summary>
        /// Sends a single <see cref="PreservationNotice"/> to a 
        /// <see cref="Custodian"/> for the current <see cref="ICase"/>.
        /// </summary>
        /// <param name="currentCase">An <see cref="ICase"/> instance.</param>
        /// <param name="custodianId">The identifier value for the <see cref="Custodian"/> to whom the <see cref="PreservationNotice"/> is inteneed for.</param>
        /// <param name="preservationNoticeTemplateId">The identifier value for the <see cref="PreservationNoticeTemplate"/> from which the <see cref="PreservationNotice"/> instance is to be built from.</param>
        /// <param name="fromAddress">The email address of the person sending the <see cref="PreservationNotice"/>.</param>
        public static void SendNotice(ICase currentCase, int custodianId,
            int preservationNoticeTemplateId, string fromAddress)
        {
            CustodianService custodianService = new CustodianService(currentCase);
            Custodian custodian = custodianService.GetCustodian(custodianId);
            PreservationNoticeSendingService.SendNotice(currentCase, custodian,
                preservationNoticeTemplateId, fromAddress, null);
        }

        /// <summary>
        /// Sends a single <see cref="PreservationNotice"/> to a   
        /// list of Custodians (<see cref="IList<Custodian>"/>) for the current <see cref="ICase"/>.
        /// </summary>
        /// <param name="currentCase">An <see cref="ICase"/> instance.</param>
        /// <param name="custodians">The list of Custodians (<see cref="IList<Custodian>"/>) who should receive the <see cref="PreservationNotice"/>.</param>
        /// <param name="preservationNoticeTemplateId">The identifier value for the <see cref="PreservationNoticeTemplate"/> from which the <see cref="PreservationNotice"/> instance is to be built from.</param>
        /// <param name="fromAddress">The email address of the person sending the <see cref="PreservationNotice"/>.</param>
        public static IList<Custodian> SendNotices(ICase currentCase, IList<Custodian> custodians,
            int preservationNoticeTemplateId, string fromAddress)
        {
            List<Custodian> custodiansWithSendError = new List<Custodian>();
            foreach (Custodian custodian in custodians)
            {
                try
                {
                    PreservationNoticeSendingService.SendNotice(currentCase, custodian,
                        preservationNoticeTemplateId, fromAddress);
                }
                catch (Exception e)
                {
                    Logger.LogError(e, currentCase);
                    custodiansWithSendError.Add(custodian);
                }
            }
            return custodiansWithSendError;
        }

        /// <summary>
        /// Sends a single <see cref="PreservationNotice"/> to a   
        /// list of Custodians (<see cref="IList<Custodian>"/>) for the current <see cref="ICase"/>.
        /// </summary>
        /// <param name="currentCase">An <see cref="ICase"/> instance.</param>
        /// <param name="custodianIdentifiers">The list of Custodian ID's (<see cref="IList<int>"/>) who should receive the <see cref="PreservationNotice"/>.</param>
        /// <param name="preservationNoticeTemplateId">The identifier value for the <see cref="PreservationNoticeTemplate"/> from which the <see cref="PreservationNotice"/> instance is to be built from.</param>
        /// <param name="fromAddress">The email address of the person sending the <see cref="PreservationNotice"/>.</param>
        public static IList<Custodian> SendNotices(ICase currentCase, IList<int> custodianIdentifiers,
            int preservationNoticeTemplateId, string fromAddress)
        {
            List<Custodian> custodians = new List<Custodian>();
            CustodianService custodianService = new CustodianService(currentCase);
            foreach (int custodianId in custodianIdentifiers)
            {
                try
                {
                    custodians.Add(custodianService.GetCustodian(custodianId));
                }
                catch (Exception e)
                {
                    Logger.LogError(e, currentCase);
                    
                }
            }
            return PreservationNoticeSendingService.SendNotices(currentCase, custodians, preservationNoticeTemplateId, fromAddress);
        }

        public static void QueueNotices(PreservationNoticeBatch batch)
        {
            // Get a unit of work context
            IUnitOfWork unitOfWork = UnitOfWork.New();

            PreservationNoticeBatchService service = new PreservationNoticeBatchService(batch.ParentCase, unitOfWork);
            service.AddPreservationNoticeBatch(batch);

            // Commit the transaction
            unitOfWork.Commit();
        }

        public static void QueueNotices(ICase currentCase,
            int preservationNoticeTemplateId,
            PreservationNoticeCriteria criteria,
            string fromAddress)
        {
            PreservationNoticeBatch batch = new PreservationNoticeBatch(preservationNoticeTemplateId,
                criteria, currentCase, fromAddress);
            PreservationNoticeSendingService.QueueNotices(batch);
        }

        public static void QueueNotices(ICase currentCase,
            int preservationNoticeTemplateId,
            string extraCriterionFieldName,
            string extraCriterionFieldValue,
            string fromAddress)
        {
            PreservationNoticeBatch batch = new PreservationNoticeBatch(preservationNoticeTemplateId,
                extraCriterionFieldName, extraCriterionFieldValue,
                currentCase, fromAddress);
            PreservationNoticeSendingService.QueueNotices(batch);
        }
    }
}
